package com.xuzhiguang.lightnat.client.transfer;

import com.xuzhiguang.lightnat.client.transfer.processor.TransferAuthenticationListener;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TransferChannelPoolImpl implements TransferChannelPool {

    private final TransferClient transferClient;

    private final int corePoolSize;

    private final long keepaliveTime;

    private final ConcurrentHashMap<String, TransferChannel> channelMap = new ConcurrentHashMap<>();

    private final LinkedBlockingQueue<TransferChannel> freeQueue = new LinkedBlockingQueue<>();

    private final LinkedBlockingQueue<TransferChannel> busyQueue = new LinkedBlockingQueue<>();

    private final Timer timer = new Timer("t-channel-pool", true);

    public TransferChannelPoolImpl(TransferClient transferClient, int corePoolSize, long keepaliveTime) {
        this.transferClient = transferClient;
        this.corePoolSize = corePoolSize;
        this.keepaliveTime = keepaliveTime;

        init();
    }

    private void init() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                    TransferChannel transferChannel = null;
                    while (channelMap.size() > corePoolSize
                            && (transferChannel = freeQueue.peek()) != null
                            && System.currentTimeMillis() > transferChannel.getLastFreeTime() + keepaliveTime) {

                        freeQueue.remove(transferChannel);
                        channelMap.remove(transferChannel.getChannel().id().asLongText());
                        transferClient.disconnect(transferChannel.getChannel());
                    }
            }
        }, 1000, 10000);
    }

    @Override
    public Channel getChannel(long timeout) {

        TransferChannel transferChannel = freeQueue.poll();

        if (transferChannel == null && transferClient.connect()) {
            try {
                transferChannel = freeQueue.poll(timeout, TimeUnit.MILLISECONDS);
            } catch (InterruptedException ignored) {

            }
        }
        if (transferChannel != null) {
            busyQueue.offer(transferChannel);
        }
        return transferChannel == null ? null : transferChannel.getChannel();
    }

    @Override
    public void recycle(Channel channel) {

        TransferChannel transferChannel = channelMap.get(channel.id().asLongText());
        if (transferChannel != null) {
            transferChannel.setLastFreeTime(System.currentTimeMillis());
            busyQueue.remove(transferChannel);
            freeQueue.offer(transferChannel);
        }
    }

    @Override
    public void remove(Channel channel) {

        TransferChannel transferChannel = channelMap.remove(channel.id().asLongText());
        if (transferChannel != null) {
            busyQueue.remove(transferChannel);
            freeQueue.remove(transferChannel);
        }

    }

    private void addTransferChannel(TransferChannel transferChannel) {
        this.channelMap.put(transferChannel.getChannel().id().asLongText(), transferChannel);
        this.freeQueue.offer(transferChannel);
    }


    public static class PoolTransferAuthenticationListener implements TransferAuthenticationListener {

        private final TransferChannelPoolImpl transferChannelPool;

        public PoolTransferAuthenticationListener(TransferChannelPoolImpl transferChannelPool) {
            this.transferChannelPool = transferChannelPool;
        }

        @Override
        public void complete(Channel channel, boolean success, String errMsg) {
            if (success) {
                TransferChannel transferChannel = new TransferChannel();
                transferChannel.setChannel(channel);
                transferChannel.setLastFreeTime(System.currentTimeMillis());

                transferChannelPool.addTransferChannel(transferChannel);
            }
        }
    }

}
